﻿using System;   
using System.IO;
using System.Web;
using System.Text;
using System.Linq;
using System.Reflection;
using System.Globalization;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Runtime.Serialization;  
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Runtime.CompilerServices;
using System.Text.Unicode;

namespace System.Extensions
{
    public static class ObjectExtensions
    {
        ///// <summary>Converts to json.</summary>
        ///// <param name="obj">The object.</param>
        ///// <param name="dateTimeFormat">The date time format.</param>
        ///// <returns>System.String.</returns>
        //public static string ToJson(this object obj, string dateTimeFormat = "yyyy-MM-dd")
        //{
        //    var options = new JsonSerializerOptions() { IgnoreNullValues = true, WriteIndented = true,Encoder = System.Text.Encodings.Web.JavaScriptEncoder.Create(UnicodeRanges.All) };
        //    options.Converters.Add(new DateTimeConverterUsingDateTimeParse(dateTimeFormat));
        //    return JsonSerializer.Serialize(obj, options);
        //}



        public static bool EqualsAny<T>(this T obj, params T[] values)
        {
            return Array.IndexOf<T>(values, obj) != -1;
        }

        public static bool EqualsNone<T>(this T obj, params T[] values)
        {
            return !obj.EqualsAny<T>(values);
        }

        public static T ConvertTo<T>(this object value)
        {
            return value.ConvertTo<T>(default);
        }

        public static T ConvertToAndIgnoreException<T>(this object value)
        {
            return value.ConvertToAndIgnoreException<T>(default);
        }

        public static T ConvertToAndIgnoreException<T>(this object value, T defaultValue)
        {
            return value.ConvertTo<T>(defaultValue, true);
        }

        public static T ConvertTo<T>(this object value, T defaultValue)
        {
            if (value != null)
            {
                Type type = typeof(T);
                if (value.GetType() == type)
                    return (T)value;
                TypeConverter converter1 = TypeDescriptor.GetConverter(value);
                if (converter1 != null && converter1.CanConvertTo(type))
                    return (T)converter1.ConvertTo(value, type);
                TypeConverter converter2 = TypeDescriptor.GetConverter(type);
                if (converter2 != null && converter2.CanConvertFrom(value.GetType()))
                    return (T)converter2.ConvertFrom(value);
            }
            return defaultValue;
        }

        public static T ConvertTo<T>(this object value, T defaultValue, bool ignoreException)
        {
            if (!ignoreException)
                return value.ConvertTo<T>();
            try
            {
                return value.ConvertTo<T>();
            }
            catch
            {
                return defaultValue;
            }
        }

        public static bool CanConvertTo<T>(this object value)
        {
            if (value != null)
            {
                Type type = typeof(T);
                TypeConverter converter1 = TypeDescriptor.GetConverter(value);
                if (converter1 != null && converter1.CanConvertTo(type))
                    return true;
                TypeConverter converter2 = TypeDescriptor.GetConverter(type);
                if (converter2 != null && converter2.CanConvertFrom(value.GetType()))
                    return true;
            }
            return false;
        }

        public static object InvokeMethod(this object obj, string methodName, params object[] parameters)
        {
            return obj.InvokeMethod<object>(methodName, parameters);
        }

        public static T InvokeMethod<T>(this object obj, string methodName, params object[] parameters)
        {
            MethodInfo method = obj.GetType().GetMethod(methodName, ((IEnumerable<object>)parameters).Select<object, Type>((Func<object, Type>)(o => o.GetType())).ToArray<Type>());
            if (method == (MethodInfo)null)
                throw new ArgumentException(string.Format("Method '{0}' not found.", (object)methodName), methodName);
            object obj1 = method.Invoke(obj, parameters);
            if (!(obj1 is T))
                return default;
            return (T)obj1;
        }

        public static object GetPropertyValue(this object obj, string propertyName)
        {
            return obj.GetPropertyValue<object>(propertyName, (object)null);
        }

        public static T GetPropertyValue<T>(this object obj, string propertyName)
        {
            return obj.GetPropertyValue<T>(propertyName, default);
        }

        public static T GetPropertyValue<T>(this object obj, string propertyName, T defaultValue)
        {
            PropertyInfo property = obj.GetType().GetProperty(propertyName);
            if (property == (PropertyInfo)null)
                throw new ArgumentException(string.Format("Property '{0}' not found.", (object)propertyName), propertyName);
            object obj1 = property.GetValue(obj, (object[])null);
            if (!(obj1 is T))
                return defaultValue;
            return (T)obj1;
        }

        public static void SetPropertyValue(this object obj, string propertyName, object value)
        {
            PropertyInfo property = obj.GetType().GetProperty(propertyName);
            if (property == (PropertyInfo)null)
                throw new ArgumentException(string.Format("Property '{0}' not found.", (object)propertyName), propertyName);
            if (!property.CanWrite)
                throw new ArgumentException(string.Format("Property '{0}' does not allow writes.", (object)propertyName), propertyName);
            property.SetValue(obj, value, (object[])null);
        }

        public static T GetAttribute<T>(this object obj) where T : Attribute
        {
            return obj.GetAttribute<T>(true);
        }

        public static T GetAttribute<T>(this object obj, bool includeInherited) where T : Attribute
        {
            if (!(obj is Type type))
                type = obj.GetType();
            return ((IEnumerable<object>)type.GetCustomAttributes(typeof(T), includeInherited)).FirstOrDefault<object>() as T;
        }

        public static IEnumerable<T> GetAttributes<T>(this object obj) where T : Attribute
        {
            return obj.GetAttributes<T>(false);
        }

        public static IEnumerable<T> GetAttributes<T>(this object obj, bool includeInherited) where T : Attribute
        {
            if (!(obj is Type type))
                type = obj.GetType();
            return type.GetCustomAttributes(typeof(T), includeInherited).OfType<T>().Select<T, T>((Func<T, T>)(attribute => attribute));
        }

        public static bool IsOfType<T>(this object obj)
        {
            return obj.IsOfType(typeof(T));
        }

        public static bool IsOfType(this object obj, Type type)
        {
            return obj.GetType().Equals(type);
        }

        public static bool IsOfTypeOrInherits<T>(this object obj)
        {
            return obj.IsOfTypeOrInherits(typeof(T));
        }

        public static bool IsOfTypeOrInherits(this object obj, Type type)
        {
            for (Type type1 = obj.GetType(); !type1.Equals(type); type1 = type1.BaseType)
            {
                if (type1 == type1.BaseType || type1.BaseType == (Type)null)
                    return false;
            }
            return true;
        }

        public static bool IsAssignableTo<T>(this object obj)
        {
            return obj.IsAssignableTo(typeof(T));
        }

        public static bool IsAssignableTo(this object obj, Type type)
        {
            Type type1 = obj.GetType();
            return type.IsAssignableFrom(type1);
        }

#pragma warning disable IDE0060 // 删除未使用的参数
        public static T GetTypeDefaultValue<T>(this T value)
#pragma warning restore IDE0060 // 删除未使用的参数
        {
            return default;
        }

        public static object ToDatabaseValue<T>(this T value)
        {
            if (!value.Equals((object)value.GetTypeDefaultValue<T>()))
                return (object)value;
            return (object)DBNull.Value;
        }

        public static T CastTo<T>(this object value)
        {
            if (value == null || !(value is T))
                return default;
            return (T)value;
        }

        public static bool IsNull(this object target)
        {
            return target.IsNull<object>();
        }

        public static bool IsNull<T>(this T target)
        {
            return ReferenceEquals((object)target, (object)null);
        }

        public static bool IsNotNull(this object target)
        {
            return target.IsNotNull<object>();
        }

        public static bool IsNotNull<T>(this T target)
        {
            return !object.ReferenceEquals((object)target, (object)null);
        }

        public static string AsString(this object target)
        {
            if (!object.ReferenceEquals(target, (object)null))
                return string.Format("{0}", target);
            return (string)null;
        }

        public static string AsString(this object target, IFormatProvider formatProvider)
        {
            return string.Format(formatProvider, "{0}", new object[1]
            {
        target
            });
        }

        public static string AsInvariantString(this object target)
        {
            return string.Format((IFormatProvider)CultureInfo.InvariantCulture, "{0}", new object[1]
            {
        target
            });
        }

        public static T NotNull<T>(this T target, T notNullValue)
        {
            if (!object.ReferenceEquals((object)target, (object)null))
                return target;
            return notNullValue;
        }

        public static T NotNull<T>(this T target, Func<T> notNullValueProvider)
        {
            if (!object.ReferenceEquals((object)target, (object)null))
                return target;
            return notNullValueProvider();
        }



        private static IEnumerable<string> ToStringDumpInternal(XContainer toXElement)
        {
            foreach (XElement xelement in (IEnumerable<XElement>)toXElement.Elements().OrderBy<XElement, string>((Func<XElement, string>)(o => o.Name.ToString())))
            {
                if (xelement.HasElements)
                {
                    foreach (string str in ObjectExtensions.ToStringDumpInternal((XContainer)xelement))
                        yield return "{" + string.Format("{0}={1}", (object)xelement.Name, (object)str) + "}";
                }
                else
                    yield return "{" + string.Format("{0}={1}", (object)xelement.Name, (object)xelement.Value) + "}";
            }
        }


        private static IEnumerable<string> ToHTMLTableInternal(XContainer xel, int padding)
        {
            yield return ObjectExtensions.FormatHTMLLine("<table>", padding);
            yield return ObjectExtensions.FormatHTMLLine("<tr><th>Attribute</th><th>Value</th></tr>", padding + 1);
            foreach (XElement xelement in (IEnumerable<XElement>)xel.Elements().OrderBy<XElement, string>((Func<XElement, string>)(o => o.Name.ToString())))
            {
                if (xelement.HasElements)
                {
                    yield return ObjectExtensions.FormatHTMLLine(string.Format("<tr><td>{0}</td><td>", (object)xelement.Name), padding + 1);
                    foreach (string str in ObjectExtensions.ToHTMLTableInternal((XContainer)xelement, padding + 2))
                        yield return str;
                    yield return ObjectExtensions.FormatHTMLLine("</td></tr>", padding + 1);
                }
                else
                    yield return ObjectExtensions.FormatHTMLLine(string.Format("<tr><td>{0}</td><td>{1}</td></tr>", (object)xelement.Name, (object)HttpUtility.HtmlEncode(xelement.Value)), padding + 1);
            }
            yield return ObjectExtensions.FormatHTMLLine("</table>", padding);
        }

        private static string FormatHTMLLine(string tag, int padding)
        {
            return string.Format("{0}{1}{2}", (object)string.Empty.PadRight(padding, '\t'), (object)tag, (object)Environment.NewLine);
        }

        //private static bool NeedRecursion(Type type, object o)
        //{
        //    if (o != null && !type.IsPrimitive && (!(o is string) && !(o is DateTime)) && (!(o is DateTimeOffset) && !(o is TimeSpan) && ((o as Delegate) is null && !(o is Enum))) && !(o is Decimal))
        //        return !(o is Guid);
        //    return false;
        //}

        //private static object GetValue(object o, PropertyInfo propertyInfo)
        //{
        //    try
        //    {
        //        return propertyInfo.GetValue(o, (object[])null);
        //    }
        //    catch
        //    {
        //        try
        //        {
        //            return propertyInfo.GetValue(o, new object[1]
        //            {
        //    (object) 0
        //            });
        //        }
        //        catch
        //        {
        //            return (object)null;
        //        }
        //    }
        //}

        //private static string CleanName(IEnumerable<char> name, bool isArray)
        //{
        //    StringBuilder stringBuilder = new StringBuilder();
        //    foreach (char ch in name.Where<char>((Func<char, bool>)(c =>
        //    {
        //        if (char.IsLetterOrDigit(c))
        //            return c != '`';
        //        return false;
        //    })).Select<char, char>((Func<char, char>)(c => c)))
        //        stringBuilder.Append(ch);
        //    if (isArray)
        //        stringBuilder.Append("Array");
        //    return stringBuilder.ToString();
        //}

        public static object DynamicCast(this object obj, Type targetType)
        {
            if (targetType.IsAssignableFrom(obj.GetType()))
                return obj;
            Type originType = obj.GetType();
            string[] names = new string[2]
            {
                    "op_Implicit",
                    "op_Explicit"
            };
            MethodInfo methodInfo = ((IEnumerable<MethodInfo>)targetType.GetMethods(BindingFlags.Static | BindingFlags.Public)).Union<MethodInfo>((IEnumerable<MethodInfo>)originType.GetMethods(BindingFlags.Static | BindingFlags.Public)).FirstOrDefault<MethodInfo>((Func<MethodInfo, bool>)(itm =>
            {
                if (itm.ReturnType.Equals(targetType) && itm.GetParameters().Length == 1 && itm.GetParameters()[0].ParameterType.IsAssignableFrom(originType))
                    return ((IEnumerable<string>)names).Contains<string>(itm.Name);
                return false;
            }));
            if (!((MethodInfo)null != methodInfo))
                throw new InvalidOperationException(string.Format("No matching cast operator found from {0} to {1}.", (object)originType.Name, (object)targetType.Name));
            return methodInfo.Invoke((object)null, new object[1]
            {
        obj
            });
        }

        public static T CastAs<T>(this object obj) where T : class, new()
        {
            return obj as T;
        }

        public static int CountLoopsToNull<T>(this T item, Func<T, T> function) where T : class
        {
            int num = 0;
            while ((item = function(item)) != null)
                ++num;
            return num;
        }

        public static K FindTypeByRecursion<T, K>(this T item, Func<T, T> function) where T : class where K : class, T
        {
            while (!((object)item is K))
            {
                if ((item = function(item)) == null)
                    return default;
            }
            return (K)(object)item;
        }

        public static T Clone<T>(this T source)
        {
            if (!typeof(T).IsSerializable)
                throw new ArgumentException("The type must be serializable.", nameof(source));
            if (object.ReferenceEquals((object)source, (object)null))
                return default;
            IFormatter formatter = (IFormatter)new BinaryFormatter();
            Stream serializationStream = (Stream)new MemoryStream();
            using (serializationStream)
            {
                formatter.Serialize(serializationStream, (object)source);
                serializationStream.Seek(0L, SeekOrigin.Begin);
                return (T)formatter.Deserialize(serializationStream);
            }
        }

        public static T Cast<T>(this object o)
        {
            if (o == null)
                throw new NullReferenceException();
            return (T)Convert.ChangeType(o, typeof(T));
        }

        public static T Cast<T>(this object o, T defaultValue)
        {
            if (o == null)
                return defaultValue;
            return (T)Convert.ChangeType(o, typeof(T));
        }

        public static void CopyPropertiesFrom(this object target, object source)
        {
            target.CopyPropertiesFrom(source, string.Empty);
        }

        public static void CopyPropertiesFrom(this object target, object source, string ignoreProperty)
        {
            target.CopyPropertiesFrom(source, new string[1]
            {
        ignoreProperty
            });
        }

        public static void CopyPropertiesFrom(this object target, object source, string[] ignoreProperties)
        {
            Type type = source.GetType();
            if (target.GetType() != type)
                throw new ArgumentException("The source type must be the same as the target");
            List<string> stringList = new List<string>();
            foreach (string ignoreProperty in ignoreProperties)
            {
                if (!string.IsNullOrEmpty(ignoreProperty) && !stringList.Contains(ignoreProperty))
                    stringList.Add(ignoreProperty);
            }
            foreach (PropertyInfo property in type.GetProperties())
            {
                if (property.CanWrite && property.CanRead && !stringList.Contains(property.Name))
                {
                    object obj = property.GetValue(source, (object[])null);
                    property.SetValue(target, obj, (object[])null);
                }
            }
        }

        public static string ToPropertiesString(this object source)
        {
            return source.ToPropertiesString(Environment.NewLine);
        }

        public static string ToPropertiesString(this object source, string delimiter)
        {
            if (source == null)
                return string.Empty;
            Type type = source.GetType();
            StringBuilder stringBuilder = new StringBuilder(type.Name);
            stringBuilder.Append(delimiter);
            foreach (PropertyInfo property in type.GetProperties())
            {
                if (property.CanWrite && property.CanRead)
                {
                    object obj = property.GetValue(source, (object[])null);
                    stringBuilder.Append(property.Name);
                    stringBuilder.Append(": ");
                    stringBuilder.Append(obj == null ? "[NULL]" : obj.ToString());
                    stringBuilder.Append(delimiter);
                }
            }
            return stringBuilder.ToString();
        }

        public static string ToXml(this object source)
        {
            return source.ToXml(Encoding.UTF8);
        }

        public static string ToXml(this object source, Encoding encoding)
        {
            if (source == null)
                throw new ArgumentException("The source object cannot be null.");
            if (encoding == null)
                throw new Exception("You must specify an encoder to use for serialization.");
            using MemoryStream memoryStream = new MemoryStream();
            new XmlSerializer(source.GetType()).Serialize((Stream)memoryStream, source);
            memoryStream.Position = 0L;
            return encoding.GetString(memoryStream.ToArray());
        }
        //public static T ToDeserializeXml<T>(this string @this)
        //{
        //    if ((object)@this == null)
        //        throw new NullReferenceException();
        //    XmlSerializer xmlSerializer = new XmlSerializer(@this.GetType());
        //    XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
        //    namespaces.Add("", "");

        //    MemoryStream mem = new MemoryStream();
        //    object obj = new object();
        //    using (XmlWriter writer = XmlWriter.Create(mem, new XmlWriterSettings() { OmitXmlDeclaration = true, Encoding = Encoding.UTF8 }))
        //    {
        //        XmlSerializer formatter = new XmlSerializer(@this.GetType());
        //        obj = formatter.Deserialize(mem);
        //    }
        //    return (T)obj;
        //}
        public static string ToXml<T>(this T @this, bool ContainsXmlHead = true)
        {
            if ((object)@this == null)
                throw new NullReferenceException();
            XmlSerializer xmlSerializer = new XmlSerializer(@this.GetType());
            XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
            namespaces.Add("", "");

            if (!ContainsXmlHead)
            {
                MemoryStream mem = new MemoryStream();
                using (XmlWriter writer = XmlWriter.Create(mem, new XmlWriterSettings() { OmitXmlDeclaration = true, Encoding = Encoding.UTF8 }))
                {
                    XmlSerializer formatter = new XmlSerializer(@this.GetType());
                    formatter.Serialize(writer, (object)@this, namespaces);
                }
                return Encoding.UTF8.GetString(mem.ToArray());
            }

            using StringWriter stringWriter = new StringWriter();
            xmlSerializer.Serialize((TextWriter)stringWriter, (object)@this, namespaces);
            return stringWriter.ToString();
        }

        public static byte[] ToBytes(this object source)
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            MemoryStream memoryStream = new MemoryStream();
            binaryFormatter.Serialize((Stream)memoryStream, source);
            return memoryStream.GetBuffer();
        }

        public static void ExceptionIfNullOrEmpty(this object value, string message, string name)
        {
            if (value == null)
                throw new ArgumentNullException(name, message);
        }

        public static string XmlSerialize<T>(this T value)
        {
            if (value == null)
            {
                return string.Empty;
            }

            var xmlserializer = new XmlSerializer(value.GetType());
            var stringWriter = new StringWriter();
            using var writer = XmlWriter.Create(stringWriter);
            xmlserializer.Serialize(writer, value);
            var uglyXml = stringWriter.ToString();

            var doc = XDocument.Parse(uglyXml);
            return doc.ToString();
        }

        public static IEnumerable<T> DistinctBy<T, C>(this IEnumerable<T> source, Func<T, C> getfield)
        {
            return source.Distinct(new Compare<T, C>(getfield));
        }
    }


    public class Compare<T, C> : IEqualityComparer<T>
    {
        private readonly Func<T, C> _getField;
        public Compare(Func<T, C> getfield)
        {
            _getField = getfield;
        }
        public bool Equals(T x, T y)
        {
            return EqualityComparer<C>.Default.Equals(_getField(x), _getField(y));
        }
        public int GetHashCode(T obj)
        {
            return EqualityComparer<C>.Default.GetHashCode(_getField(obj));
        }
    }


    public class DateTimeConverterUsingDateTimeParse : JsonConverter<DateTime>
    {
        /// <summary>
        /// 日期格式
        /// </summary>
        public string DateTimeFormat { get; }
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="dateTimeFormat"></param>
        public DateTimeConverterUsingDateTimeParse(string dateTimeFormat)
        {
            this.DateTimeFormat = dateTimeFormat;
        }
        public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        { 
            return DateTime.Parse(reader.GetString());
        }

        public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.ToString(DateTimeFormat));
        }
    }


}
